#include "src/scene/raytracing.h"

#include "src/scene/scene.h"
#include "src/scene/light.h"
#include "src/scene/hbb/tree.h"

#include <iostream>

Color Raytracing::calculate(const Ray &_ray, unsigned int _depth, bool) const{
    //adiciona mais nada
    if(_depth == 0)
        return Color(0.0, 0.0, 0.0, 0.0);

    //achando o ponto mais proximo
    NearestInfo nearest;// = findNearest(_ray);

    if(!scene->getTree()->hit(_ray, &nearest))
        return scene->getBackgroundColor();

    return findColor(nearest, _ray.getDirection(), _depth, _ray.isInsideOf());
}

Color Raytracing::findColor(const NearestInfo &_nearest, const Vector3D &_incident, unsigned int _depth, Object *_wasInsideIn) const
{

    #if DEBUG
        if(!_incident.isNormalized()) std::cout << "vetor incidente não é normalizado" << std::endl;
        if(!_nearest.normal.isNormalized()) std::cout << "vetor normal não é normalizado" << std::endl;
    #endif //DEBUG

    //!existem n casos
    //!- o raio está vindo do ar e bate em um objeto opaco (mais usual)
    //!     . reflexao
    //!- o raio está vindo do ar e bate em um objeto transparente/translucido
    //!     . reflexao e refração (proporcionais à medida de reflectancia)
    //!         - a normal NAO é invertida
    //!- o raio está vindo de um objeto transparente/translucido e vai para o ar
    //!     . refração e reflexao
    //!         - a normal É invertida
    //!- o raio está vindo de um objeto transparente/translucido e bate em outro objeto opaco
    //!     . reflexçao
    //!- o raio está vindo de um objeto transparente/translucido e bate em outro objeto translucido
    //!     . reflexçao e refração


    Color reflectedColor(0, 0, 0, 0);
    Color transmittedColor(0, 0, 0, 0);
    Material nearestMaterial = _nearest.object->getMaterial();
    if(!_wasInsideIn)//vinha do ar
    {
        //cor de reflexão, vai ser a mesma pros dois casos
        if(nearestMaterial.ks != 0){
            reflectedColor = findReflectionColor(_nearest.point, _nearest.normal, _incident, _depth - 1);
        }


        //objeto transparente/translucido
        //ganha  reflexão e a refração
        if( (nearestMaterial.kt != 0 && nearestMaterial.refractiveIndex != 0) ){

            double n1 = AIR_REFRACTION_INDEX;
            double n2 = nearestMaterial.refractiveIndex;
            double reflectance = findReflectance(_nearest.normal, _incident, n1, n2);

            //em caso de o raio não entrar por causa do angulo, conta so com a reflexao
            if(reflectance != -1)
            {
                transmittedColor = findTransmittionColor(_nearest.point, _nearest.normal, _incident,
                                                               n1, n2, _depth - 1, _nearest.object);

                transmittedColor = (transmittedColor * (1 - reflectance));
                reflectedColor = (reflectedColor * reflectance);
            }
        }
        //objeto opaco
        //ganha só a reflexao
//        else {
//            color = color + reflectedColor;
//        }
    }
    else// vinha de dentro de algum objeto
    {
        //estava dentro do objeto e saiu dele para o ar
        //ganha reflexao e refração
        if(_wasInsideIn == _nearest.object)
        {
            //a reflexão vai precisar gerar o raio com o objeto em que estava dentro
            if(nearestMaterial.ks != 0){
                reflectedColor = findReflectionColor(_nearest.point, _nearest.normal, _incident, _depth - 1, _nearest.object);
            }

            double n1 = nearestMaterial.refractiveIndex;
            double n2 = AIR_REFRACTION_INDEX;  //************ isso não é verdade!!! , pode ser que ele esteja saindo em outro objeto
            double reflectance = findReflectance(_nearest.normal, _incident, n1, n2);

            //em caso de o raio não entrar por causa do angulo, conta so com a reflexao
//            if(reflectance == -1)
//            {
//                color = color + reflectedColor;
//            }
            //caso em que tem de fato a reflexao
//            else
            if(reflectance != -1)
            {
                //se ele está dentro então  obviamente ele pode fazer refração
                transmittedColor = findTransmittionColor(_nearest.point, _nearest.normal, _incident,
                                                               n1, n2, _depth - 1);

                transmittedColor = (transmittedColor * (1 - reflectance));
                reflectedColor = (reflectedColor * reflectance);
            }
        }
        //estava dentro do objeto e bateu em outro
        else
        {
            //independnete do tipo de objeto, vai ter refração
            if(nearestMaterial.ks != 0){
                reflectedColor = findReflectionColor(_nearest.point, _nearest.normal, _incident, _depth - 1);
            }

            //bate em um objeto transparente/translucido
            //ganha  reflexão e a refração
            if( (nearestMaterial.kt != 0 && nearestMaterial.refractiveIndex != 0) ){

                double n1 = _wasInsideIn->getMaterial().refractiveIndex;
                double n2 = nearestMaterial.refractiveIndex;
                double reflectance = findReflectance(_nearest.normal, _incident, n1, n2);

                //em caso de o raio não entrar por causa do angulo, conta so com a reflexao
//                if(reflectance == -1)
//                {
//                    color = color + reflectedColor;
//                }
//                //caso em que tem de fato a reflexao
//                else
                if(reflectance != -1)
                {
                    transmittedColor = findTransmittionColor(_nearest.point, _nearest.normal, _incident,
                                                                   n1, n2, _depth - 1, _nearest.object);
                    transmittedColor = (transmittedColor * (1 - reflectance));
                    reflectedColor = (reflectedColor * reflectance);
                }
            }
            //objeto opaco
            //ganha só a reflexao
//            else {
//                color = color + reflectedColor;
//            }
        }
    }
    Color color = (reflectedColor * nearestMaterial.ks) + (transmittedColor * nearestMaterial.kt);
//    color = color + reflectedColor + transmittedColor;

    const std::vector<Light *> lights = scene->getLights();
    HBBTree *tree = scene->getTree();

    Vector3D view = (scene->getCamera()->getEye() - _nearest.point).normalize();
    color = color + (lights[0]->getIntersectionColor(_nearest, view, tree));//ambiente
    Color lightColor(0.0, 0.0, 0.0, 0.0);

    //!colocando a cor de todas as luzes
    unsigned int lsize = lights.size();
    for(unsigned int i = 1; i < lsize; i++){//aqui ja inclui uma luz ambiente!
        lightColor = lightColor + (lights[i]->getIntersectionColor(_nearest, view, tree));

        //TODO: testar dividir a cor da luz por lsize;
    }
    color = color + (lightColor/((double)lsize - 1.0));

    if(nearestMaterial.useTexture() && (nearestMaterial.texture->coloringType == MULTIPLY))
    {
        color =
            color *
            _nearest.texture;
    }

    return color.truncate();
}

//Color Raytracing::findColor(const NearestInfo &_nearest, const Vector3D &_incident, unsigned int _depth, Object *_wasInsideIn) const
//{
//
//    #if DEBUG
//        if(!_incident.isNormalized()) std::cout << "vetor incidente não é normalizado" << std::endl;
//        if(!_nearest.normal.isNormalized()) std::cout << "vetor normal não é normalizado" << std::endl;
//    #endif //DEBUG
//
//    //!existem n casos
//    //!- o raio está vindo do ar e bate em um objeto opaco (mais usual)
//    //!     . reflexao
//    //!- o raio está vindo do ar e bate em um objeto transparente/translucido
//    //!     . reflexao e refração (proporcionais à medida de reflectancia)
//    //!         - a normal NAO é invertida
//    //!- o raio está vindo de um objeto transparente/translucido e vai para o ar
//    //!     . refração e reflexao
//    //!         - a normal É invertida
//    //!- o raio está vindo de um objeto transparente/translucido e bate em outro objeto opaco (NAO IMPLEMENTADA)
//    //!     . reflexçao
//    //!- o raio está vindo de um objeto transparente/translucido e bate em outro objeto translucido (NAO IMPLEMENTADA)
//    //!     . reflexçao e refração
//
//
//    Color color(0, 0, 0, 0);
//    Material nearestMaterial = _nearest.object->getMaterial();
//    if(!_wasInsideIn)//vinha do ar
//    {
//        //cor de reflexão, vai ser a mesma pros dois casos
//        Color reflectedColor(0, 0, 0, 0);
//        if(nearestMaterial.ks != 0){
//            reflectedColor = findReflectionColor(_nearest.point, _nearest.normal, _incident, _depth - 1);
//        }
//
//
//        //objeto transparente/translucido
//        //ganha  reflexão e a refração
//        if( (nearestMaterial.kt != 0 && nearestMaterial.refractiveIndex != 0) ){
//
//            double n1 = AIR_REFRACTION_INDEX;
//            double n2 = nearestMaterial.refractiveIndex;
//            double reflectance = findReflectance(_nearest.normal, _incident, n1, n2);
//
//            //em caso de o raio não entrar por causa do angulo, conta so com a reflexao
//            if(reflectance == -1)
//            {
//                color = color + reflectedColor;
//            }
//            //caso em que tem de fato a reflexao
//            else
//            {
//                Color transmittedColor = findTransmittionColor(_nearest.point, _nearest.normal, _incident,
//                                                               n1, n2, _depth - 1, _nearest.object);
//                color = color + (reflectedColor * reflectance);
//                color = color + (transmittedColor * (1 - reflectance));
//            }
//        }
//        //objeto opaco
//        //ganha só a reflexao
//        else {
//            color = color + reflectedColor;
//        }
//    }
//    else// vinha de dentro de algum objeto
//    {
//        //estava dentro do objeto e saiu dele para o ar
//        //ganha reflexao e refração
//        if(_wasInsideIn == _nearest.object)
//        {
//            //a reflexão vai precisar gerar o raio com o objeto em que estava dentro
//            Color reflectedColor(0, 0, 0, 0);
//            if(nearestMaterial.ks != 0){
//                reflectedColor = findReflectionColor(_nearest.point, _nearest.normal, _incident, _depth - 1, _nearest.object);
//            }
//
//            double n1 = nearestMaterial.refractiveIndex;
//            double n2 = AIR_REFRACTION_INDEX;
//            double reflectance = findReflectance(_nearest.normal, _incident, n1, n2);
//
//            //em caso de o raio não entrar por causa do angulo, conta so com a reflexao
//            if(reflectance == -1)
//            {
//                color = color + reflectedColor;
//            }
//            //caso em que tem de fato a reflexao
//            else
//            {
//                //se ele está dentro então  obviamente ele pode fazer refração
//                Color transmittedColor = findTransmittionColor(_nearest.point, _nearest.normal, _incident,
//                                                               n1, n2, _depth - 1);
//
//                color = color + (reflectedColor * reflectance);
//                color = color + (transmittedColor * (1 - reflectance));
//            }
//        }
//        else
//        {
//            //independnete do tipo de objeto, vai ter refração
//            Color reflectedColor(0, 0, 0, 0);
//            if(nearestMaterial.ks != 0){
//                reflectedColor = findReflectionColor(_nearest.point, _nearest.normal, _incident, _depth - 1);
//            }
//
//            //bate em um objeto transparente/translucido
//            //ganha  reflexão e a refração
//            if( (nearestMaterial.kt != 0 && nearestMaterial.refractiveIndex != 0) ){
//
//                double n1 = _wasInsideIn->getMaterial().refractiveIndex;
//                double n2 = nearestMaterial.refractiveIndex;
//                double reflectance = findReflectance(_nearest.normal, _incident, n1, n2);
//
//                //em caso de o raio não entrar por causa do angulo, conta so com a reflexao
//                if(reflectance == -1)
//                {
//                    color = color + reflectedColor;
//                }
//                //caso em que tem de fato a reflexao
//                else
//                {
//                    Color transmittedColor = findTransmittionColor(_nearest.point, _nearest.normal, _incident,
//                                                                   n1, n2, _depth - 1, _nearest.object);
//                    color = color + (reflectedColor * reflectance);
//                    color = color + (transmittedColor * (1 - reflectance));
//                }
//            }
//            //objeto opaco
//            //ganha só a reflexao
//            else {
//                color = color + reflectedColor;
//            }
//        }
//    }
//
//
//    Vector3D view = (scene->getCamera()->getEye() - _nearest.point).normalize();
//
//    const std::vector<Light *> lights = scene->getLights();
//    HBBTree *tree = scene->getTree();
//
//    //!colocando a cor de todas as luzes
//    unsigned int lsize = lights.size();
//    for(unsigned int i = 0; i < lsize; i++){//aqui ja inclui uma luz ambiente!
//        color = color + (lights[i]->getIntersectionColor(_nearest, view, tree));
//
//        //TODO: testar dividir a cor da luz por lsize;
//    }
//
//    if(nearestMaterial.useTexture() && (nearestMaterial.texture->coloringType == MULTIPLY))
//    {
//        color =
//            color *
//            _nearest.texture;
//    }
//
//    return color.truncate();
//}
